package cc.shacocloud.mirage.restful.bind.converter;

import cc.shacocloud.mirage.restful.HttpRequest;
import cc.shacocloud.mirage.restful.HttpResponse;
import cc.shacocloud.mirage.restful.http.HttpHeaderMap;
import cc.shacocloud.mirage.restful.http.HttpMessageConverter;
import cc.shacocloud.mirage.restful.http.MediaType;
import cc.shacocloud.mirage.utils.MethodParameter;
import cc.shacocloud.mirage.utils.collection.CollUtil;
import io.vertx.core.Future;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpHeaders;
import org.jetbrains.annotations.Nullable;

import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * {@link HttpMessageConverter}实现的抽象基类。
 *
 * <p>
 * 这个基类通过 {@link #setSupportedMediaTypes(List) supportedMediaTypes} bean属性添加了对设置{@code MediaTypes}的支持。
 * 它还在写入输出消息时增加了对{@link HttpHeaders#CONTENT_TYPE}和{@link HttpHeaders#CONTENT_LENGTH}的支持。
 *
 * @param <T> 转换后的对象类型
 */
public abstract class AbstractHttpMessageConverter<T> implements HttpMessageConverter<T> {
    
    private List<MediaType> supportedMediaTypes = Collections.emptyList();
    
    @Nullable
    private Charset defaultCharset;
    
    
    /**
     * 构造一个不支持媒体类型的{@code AbstractHttpMessageConverter}。
     *
     * @see #setSupportedMediaTypes
     */
    protected AbstractHttpMessageConverter() {
    }
    
    /**
     * 构造具有一种支持的媒体类型的{@code AbstractHttpMessageConverter}。
     *
     * @param supportedMediaType 支持的媒体类型
     */
    protected AbstractHttpMessageConverter(MediaType supportedMediaType) {
        setSupportedMediaTypes(Collections.singletonList(supportedMediaType));
    }
    
    /**
     * 构造具有多种支持媒体类型的{@code AbstractHttpMessageConverter}。
     *
     * @param supportedMediaTypes 支持的媒体类型
     */
    protected AbstractHttpMessageConverter(MediaType... supportedMediaTypes) {
        setSupportedMediaTypes(Arrays.asList(supportedMediaTypes));
    }
    
    /**
     * 构造带有默认字符集和多种支持的媒体类型的{@code AbstractHttpMessageConverter}。
     *
     * @param defaultCharset      默认字符集
     * @param supportedMediaTypes 支持的媒体类型
     */
    protected AbstractHttpMessageConverter(@Nullable Charset defaultCharset, MediaType... supportedMediaTypes) {
        this.defaultCharset = defaultCharset;
        setSupportedMediaTypes(Arrays.asList(supportedMediaTypes));
    }
    
    @Override
    public List<MediaType> getSupportedMediaTypes() {
        return Collections.unmodifiableList(this.supportedMediaTypes);
    }
    
    /**
     * 设置此转换器支持的{@link MediaType}对象列表
     */
    public void setSupportedMediaTypes(List<MediaType> supportedMediaTypes) {
        if (CollUtil.isEmpty(supportedMediaTypes)) {
            throw new IllegalArgumentException("supportedMediaTypes 列表不能为空");
        }
        this.supportedMediaTypes = new ArrayList<>(supportedMediaTypes);
    }
    
    /**
     * 如果有的话，返回默认字符集。
     */
    @Nullable
    public Charset getDefaultCharset() {
        return this.defaultCharset;
    }
    
    /**
     * 设置默认字符集(如果有的话)。
     */
    public void setDefaultCharset(@Nullable Charset defaultCharset) {
        this.defaultCharset = defaultCharset;
    }
    
    /**
     * 这个实现检查给定的类是否为{@linkplain #supports }，
     * 以及{@linkplain #getSupportedMediaTypes } {@linkplain MediaType#includes }给定的媒体类型。
     */
    @Override
    public boolean canRead(MethodParameter parameter, Class<?> clazz, @Nullable MediaType mediaType) {
        return supports(clazz) && canRead(mediaType);
    }
    
    /**
     * 如果任何{@linkplain #setSupportedMediaTypes }媒体类型包括{@link MediaType#includes}给定的媒体类型，返回{@code true}
     *
     * @param mediaType 要读取的媒体类型，如果没有指定，可以是{@code null}。通常是{@code Content-Type}头的值。
     * @return 支持的媒体类型与媒体类型兼容或媒体类型为{@code null}则为{@code true}
     */
    protected boolean canRead(@Nullable MediaType mediaType) {
        if (mediaType == null) {
            return true;
        }
        for (MediaType supportedMediaType : getSupportedMediaTypes()) {
            if (supportedMediaType.includes(mediaType)) {
                return true;
            }
        }
        return false;
    }
    
    /**
     * 这个实现检查给定的类是否 {@linkplain #supports }，
     * 以及 {@linkplain #getSupportedMediaTypes}媒体类型 {@linkplain MediaType#includes}包含给定的媒体类型。
     */
    @Override
    public boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) {
        return supports(clazz) && canWrite(mediaType);
    }
    
    /**
     * 如果给定的媒体类型包括任何{@linkplain #setSupportedMediaTypes(List) }，则返回{@code true}。
     *
     * @param mediaType 要写入的媒体类型，如果没有指定，可以是{@code null}。通常是{@code Accept}头的值。
     * @return 支持的媒体类型与媒体类型兼容或媒体类型为{@code null}则为{@code true}
     */
    protected boolean canWrite(@Nullable MediaType mediaType) {
        if (mediaType == null || MediaType.ALL.equalsTypeAndSubtype(mediaType)) {
            return true;
        }
        for (MediaType supportedMediaType : getSupportedMediaTypes()) {
            if (supportedMediaType.isCompatibleWith(mediaType)) {
                return true;
            }
        }
        return false;
    }
    
    /**
     * 这个实现简单地委托给{@link #readInternal(Class, HttpRequest)}。以后的实现可能会添加一些默认行为。
     */
    @Override
    public final Future<T> read(MethodParameter parameter, Class<? extends T> clazz, HttpRequest request) {
        return readInternal(clazz, request);
    }
    
    /**
     * 这个实现通过调用{@link #addDefaultHeaders}来设置默认的头，然后调用{@link #writeInternal}。
     */
    @Override
    public final Future<Buffer> write(final T t, @Nullable MediaType contentType, HttpResponse response) {
        final HttpHeaderMap headers = response.headers();
        
        // 设置默认头
        addDefaultHeaders(headers, t, contentType);
        
        return writeInternal(t, response);
    }
    
    /**
     * 向输出消息添加默认标题。
     * <p>
     * 如果没有提供内容类型，此实现将委托给{@link #getDefaultContentType(Object)}，必要时设置默认字符集。
     * 调用{@link #getContentLength}，并设置相应的头部。
     */
    protected void addDefaultHeaders(HttpHeaderMap headers, T t, @Nullable MediaType contentType) {
        if (headers.getContentType() == null) {
            MediaType contentTypeToUse = contentType;
            
            if (contentType == null || !contentType.isConcrete()) {
                contentTypeToUse = getDefaultContentType(t);
            } else if (MediaType.APPLICATION_OCTET_STREAM.equals(contentType)) {
                MediaType mediaType = getDefaultContentType(t);
                contentTypeToUse = (mediaType != null ? mediaType : contentTypeToUse);
            }
            
            if (contentTypeToUse != null) {
                if (contentTypeToUse.getCharset() == null) {
                    Charset defaultCharset = getDefaultCharset();
                    if (defaultCharset != null) {
                        contentTypeToUse = new MediaType(contentTypeToUse, defaultCharset);
                    }
                }
                headers.setContentType(contentTypeToUse);
            }
        }
        if (headers.getContentLength() < 0 && !headers.contains(HttpHeaders.TRANSFER_ENCODING)) {
            Long contentLength = getContentLength(t, headers.getContentType());
            if (contentLength != null) {
                headers.setContentLength(contentLength);
            }
        }
    }
    
    /**
     * 返回给定类型的默认内容类型。
     * <p>
     * <p>
     * 默认情况下，它返回{@link #setSupportedMediaTypes(List)}属性的第一个元素(如果有的话)。
     * 可以在子类中被重写。
     *
     * @param t the type to return the content type for
     * @return the content type, or {@code null} if not known
     */
    @Nullable
    protected MediaType getDefaultContentType(T t) {
        List<MediaType> mediaTypes = getSupportedMediaTypes();
        return (!mediaTypes.isEmpty() ? mediaTypes.get(0) : null);
    }
    
    /**
     * 返回给定类型的内容长度
     * <p>
     * 默认情况下，这会返回{@code null}，这意味着内容长度是未知的。可以在子类中被重写。
     *
     * @param t 返回内容长度的类型
     * @return 内容长度， (如果不知道{@code null})
     */
    @Nullable
    protected Long getContentLength(T t, @Nullable MediaType contentType) {
        return null;
    }
    
    /**
     * 指示此转换器是否支持给定的类。
     *
     * @param clazz 要测试以获得支持的类
     * @return 如果支持则为{@code true}反之为{@code false}
     */
    protected abstract boolean supports(Class<?> clazz);
    
    /**
     * 读取实际对象的抽象模板方法。从{@link HttpMessageConverter#read}调用。
     *
     * @param clazz   要返回的对象类型
     * @param request 要从中读取的HTTP输入消息
     * @return 转换对象
     */
    protected abstract Future<T> readInternal(Class<? extends T> clazz, HttpRequest request);
    
    /**
     * 编写实际主体的抽象模板方法。从{@link HttpMessageConverter#write}调用。
     *
     * @param t        要写入输出消息的对象
     * @param response 要写入的HTTP输出消息
     * @return {@link } 解析后的结果
     */
    protected abstract Future<Buffer> writeInternal(T t, HttpResponse response);
    
}
